[Chapter Thirteen][Previous]
[Next] [Art of
Assembly][Randall Hyde]
Art of Assembly: Chapter Thirteen
- 13.4 - UCR Standard Library File I/O Routines
- 13.4.1 - Fopen
- 13.4.2 - Fcreate
- 13.4.3 - Fclose
- 13.4.4 - Fflush
- 13.4.5 - Fgetc
- 13.4.6 - Fread
- 13.4.7 - Fputc
- 13.4.8 - Fwrite
- 13.4.9 - Redirecting I/O Through the
StdLib File I/O Routines
13.4 UCR Standard Library File I/O Routines
Although MS-DOS' file I/O facilities are not too bad, the UCR Standard
Library provides a file I/O package which makes blocked sequential I/O as
easy as character at a time file I/O. Furthermore, with a tiny amount of
effort, you can use all the StdLib routines like printf, print, puti,
puth, putc, getc, gets,
etc., when performing file I/O. This greatly
simplifies text file operations in assembly language.
Note that record oriented, or binary I/O, is probably best left to pure
DOS. any time you want to do random access within a file. The Standard Library
routines really only support sequential text I/O. Nevertheless, this is
the most common form of file I/O around, so the Standard Library routines
are quite useful indeed.
The UCR Standard Library provides eight file I/O routines: fopen
,
fcreate
, fclose
, fgetc
, fread
,
fputc
, and fwrite
. Fgetc
and fputc
perform character at a time I/O, fread
and fwrite
let you read and write blocks of data, the other four functions perform
the obvious DOS operations.
The UCR Standard Library uses a special file variable to keep track of file
operations. There is a special record type, FileVar, declared in stdlib.a[8].
When using the StdLib file I/O routines you must create a variable of type
FileVar
for every file you need open at the same time. This
is very easy, just use a definition of the form:
MyFileVar FileVar {}
Please note that a Standard Library file variable is not the same thing
as a DOS file handle. It is a structure which contains the DOS file handle,
a buffer (for blocked I/O), and various index and status variables. The
internal structure of this type is of no interest (remember data encapsulation!)
except to the implementor of the file routines. You will pass the address
of this file variable to the various Standard Library file I/O routines.
13.4.1 Fopen
Entry parameters:
ax- File open mode
0- File opened for reading
1- File opened for writing
dx:si- Points at a zero terminated string containing
the filename.
es:di- Points at a StdLib file variable.
Exit parameters:
If the carry is set,
ax contains the returned DOS error code
(see DOS open function).
Fopen
opens a sequential text file for reading or writing.
Unlike DOS, you cannot open a file for reading and writing. Furthermore,
this is a sequential text file which does not support random access. Note
that the file must exist or fopen
will return an error. This
is even true when you open the file for writing.
Note that if you open a file for writing and that file already exists, any
data written to the file will overwrite the existing data. When you close
the file, any data appearing in the file after the data you wrote will still
be there. If you want to erase the existing file before writing data to
it, use the fcreate
function.
13.4.2 Fcreate
Entry parameters:
dx:si- Points at a zero terminated string containing
the filename.
es:di- Points at a StdLib file variable.
Exit parameters:
If the carry is set,
ax contains the returned DOS error code
(see DOS open function).
Fcreate
creates a new file and opens it for writing. If the
file already exists, fcreate
deletes the existing file and
creates a new one. It initializes the file variable for output but is otherwise
identical to the fopen
call.
13.4.3 Fclose
Entry parameters:
es:di- Points at a StdLib file variable.
Exit parameters:
If the carry is set,
ax contains the returned DOS error code
(see DOS open function).
Fclose
closes a file and updates any internal housekeeping
information. It is very important that you close all files opened with fopen
or fcreate
using this call. When making DOS file calls,
if you forget to close a file DOS will automatically do that for you when
your program terminates. However, the StdLib routines cache up data in internal
buffers. the fclose call automatically flushes these buffers to disk. If
you exit your program without calling fclose
, you may lose
some data written to the file but not yet transferred from the internal
buffer to the disk.
If you are in an environment where it is possible for someone to abort the
program without giving you a chance to close the file, you should call the
fflush routines (see the next section) on a regular basis to avoid losing
too much data.
13.4.4 Fflush
Entry parameters:
es:di- Points at a StdLib file variable.
Exit parameters:
If the carry is set,
ax contains the returned DOS error code
(see DOS open function).
This routine immediately writes any data in the internal file buffer to
disk. Note that you should only use this routine in conjunction with files
opened for writing (or opened by fcreate
). If you write data
to a file and then need to leave the file open, but inactive, for some time
period, you should perform a flush operation in case the program terminates
abnormally.
13.4.5 Fgetc
Entry parameters:
es:di- Points at a StdLib file variable.
Exit parameters:
If the carry flag is clear,
al contains the character read from the file.
If the carry is set,
ax contains the returned DOS error code
(see DOS open function). ax will contain zero if you attempt to read beyond the end of file.
Fgetc
reads a single character from the file and returns this
character in the al
register. Unlike calls to DOS, single character
I/O using fgetc
is relatively fast since the StdLib routines
use blocked I/O. Of course, multiple calls to fgetc
will never
be faster than a call to fread
(see the next section), but
the performance is not too bad.
Fgetc
is very flexible. As you will see in a little bit, you
may redirect the StdLib input routines to read their data from a file using
fgetc
. This lets you use the higher level routines like gets
and getsm
when reading data from a file.
13.4.6 Fread
Entry parameters:
es:di- Points at a StdLib file variable.
dx:si- Points at an input data buffer.
cx- Contains a byte count.
Exit parameters:
If the carry flag is clear,
ax contains the actual number of bytes
read from the file.
If the carry is set,
ax contains the returned DOS error code
(see DOS open function).
Fread
is very similar to the DOS read
command.
It lets you read a block of bytes, rather than just one byte, from a file.
Note that if all you are doing is reading a block of bytes from a file,
the DOS call is slightly more efficient than fread
. However,
if you have a mixture of single byte reads and multi-byte reads, the combination
of fread
and fgetc
work very well.
As with the DOS read operation, if the byte count returned in ax
does
not match the value passed in the cx
register, then you've
read the remaining bytes in the file. When this occurs, the next call to
fread
or fgetc
will return an EOF error (carry
will be set and ax
will contain zero). Note that fread
does not return EOF unless there were zero bytes read from the file.
13.4.7 Fputc
Entry parameters:
es:di- Points at a StdLib file variable.
al- Contains the character to write to the file.
Exit parameters:
If the carry is set,
ax contains the returned DOS error code
(see DOS open function).
Fputc
writes a single character (in al
) to the
file specified by the file variable whose address is in es:di
.
This call simply adds the character in al
to an internal buffer
(part of the file variable) until the buffer is full. Whenever the buffer
is filled or you call fflush
(or close the file with fclose
),
the file I/O routines write the data to disk.
13.4.8 Fwrite
Entry parameters:
es:di- Points at a StdLib file variable.
dx:si- Points at an output data buffer.
cx- Contains a byte count.
Exit parameters:
If the carry flag is clear,
ax contains the actual number of bytes
written to the file.
If the carry is set,
ax contains the returned DOS error code
(see DOS open function).
Like fread
, fwrite
works on blocks of bytes. It
lets you write a block of bytes to a file opened for writing with fopen
or fcreate.
13.4.9 Redirecting I/O Through the StdLib File I/O Routines
The Standard Library provides very few file I/O routines. Fputc
and fwrite
are the only two output routines, for example. The
"C" programming language standard library (on which the UCR Standard
Library is based) provides many routines like fprintf, fputs, fscanf,
etc. None of these are necessary in the UCR Standard Library because
the UCR library provides an I/O redirection mechanism that lets you reuse
all existing I/O routines to perform file I/O.
The UCR Standard Library putc
routine consists of a single
jmp
instruction. This instruction transfers control to some
actual output routine via an indirect address internal to the putc code.
Normally, this pointer variable points at a piece of code which writes the
character in the al
register to the DOS standard output device.
However, the Standard Library also provides four routines which let you
manipulate this indirect pointer. By changing this pointer you can redirect
the output from its current routine to a routine of your choosing. All Standard
Library output routines (e.g., printf, puti, puth, puts
) call
putc
to output individual characters. Therefore, redirecting
the putc
routine affects all the output routines.
Likewise, the getc
routine is nothing more than an indirect
jmp
whose pointer variable normally points at a piece of code
which reads data from the DOS standard input. Since all Standard Library
input routines call the getc
function to read each character
you can redirect file input in a manner identical to file output.
The Standard Library GetOutAdrs, SetOutAdrs, PushOutAdrs,
and
PopOutAdrs
are the four main routines which manipulate the
output redirection pointer. GetOutAdrs
returns the address
of the current output routine in the es:di
registers. Conversely,
SetOutAdrs
expects you to pass the address of a new output
routine in the es:di
registers and it stores this address into
the output pointer. PushOutAdrs
and PopOutAdrs
push and pop the pointer on an internal stack. These do not use the 80x86's
hardware stack. You are limited to a small number of pushes and pops. Generally,
you shouldn't count on being able to push more than four of these addresses
onto the internal stack without overflowing it.
GetInAdrs, SetInAdrs, PushInAdrs,
and PopInAdrs
are the complementary routines for the input vector. They let you manipulate
the input routine pointer. Note that the stack for PushInAdrs/PopInAdrs
is not the same as the stack for PushOutAdrs/PopOutAdrs
. Pushes
and pops to these two stacks are independent of one another.
Normally, the output pointer (which we will henceforth refer to as the output
hook) points at the Standard Library routine PutcStdOut[9]
.
Therefore, you can return the output hook to its normal initialization state
at any time by executing the statements[10]:
mov di, seg SL_PutcStdOut
mov es, di
mov di, offset SL_PutcStdOut
SetOutAdrs
The PutcStdOut
routine writes the character in the al
register to the DOS standard output, which itself might be redirected
to some file or device (using the ">" DOS redirection operator).
If you want to make sure your output is going to the video display, you
can always call the PutcBIOS
routine which calls the BIOS directly
to output a character[11]. You can force all
Standard Library output to the standard error device using a code sequence
like:
mov di, seg SL_PutcBIOS
mov es, di
mov di, offset SL_PutcBIOS
SetOutAdrs
Generally, you would not simply blast the output hook by storing a pointer
to your routine over the top of whatever pointer was there and then restoring
the hook to PutcStdOut
upon completion. Who knows if the hook
was pointing at PutcStdOut
in the first place? The best solution
is to use the Standard Library PushOutAdrs
and PopOutAdrs
routines to preserve and restore the previous hook. The following code demonstrates
a gentler way of modifying the output hook:
PushOutAdrs ;Save current output routine.
mov di, seg Output_Routine
mov es, di
mov di, offset Output_Routine
SetOutAdrs
<Do all output to Output_Routine here>
PopOutAdrs ;Restore previous output routine.
Handle input in a similar fashion using the corresponding input hook access
routines and the SL_GetcStdOut
and SL_GetcBIOS
routines. Always keep in mind that there are a limited number of entries
on the input and output hook stacks so what how many items you push onto
these stacks without popping anything off.
To redirect output to a file (or redirect input from a file) you must first
write a short routine which writes (reads) a single character from (to)
a file. This is very easy. The code for a subroutine to output data to a
file described by file variable OutputFile
is
ToOutput proc far
push es
push di
; Load ES:DI with the address of the OutputFile variable. This
; code assumes OutputFile is of type FileVar, not a pointer to
; a variable of type FileVar.
mov di, seg OutputFile
mov es, di
mov di, offset OutputFile
; Output the character in AL to the file described by "OutputFile"
fputc
pop di
pop es
ret
ToOutput endp
Now with only one additional piece of code, you can begin writing data to
an output file using all the Standard Library output routines. That is a
short piece of code which redirects the output hook to the "ToOutput
"
routine above:
SetOutFile proc
push es
push di
PushOutAdrs ;Save current output hook.
mov di, seg ToOutput
mov es, di
mov di, offset ToOutput
SetOutAdrs
pop di
pop es
ret
SetOutFile endp
There is no need for a separate routine to restore the output hook to its
previous value; PopOutAdrs
will handle that task by itself.
[8] Actually, it's declared in file.a. Stdlib.a
includes file.a so this definition appears inside stdlib.a as well.
[9] Actually, the routine is SL_PutcStdOut. The Standard
Library macro by which you would normally call this routine is PutcStdOut.
[10] If you do not have any calls to PutcStdOut
in your program, you will also need to add the statement "externdef
SL_PutcStdOut:far" to your program.
[11]
It is possible to redirect even the BIOS output, but this is rarely done
and not easy to do from DOS.
- 13.4 - UCR Standard Library File I/O Routines
- 13.4.1 - Fopen
- 13.4.2 - Fcreate
- 13.4.3 - Fclose
- 13.4.4 - Fflush
- 13.4.5 - Fgetc
- 13.4.6 - Fread
- 13.4.7 - Fputc
- 13.4.8 - Fwrite
- 13.4.9 - Redirecting I/O Through the
StdLib File I/O Routines
Art of Assembly: Chapter Thirteen - 28 SEP 1996
[Chapter Thirteen][Previous]
[Next] [Art of
Assembly][Randall Hyde]